package viewer;

import javax.swing.*;

import model.GeometryModel3D;

import java.io.*;
import java.net.*;
import java.awt.*;
import java.awt.event.*;

public class JRoff extends JFrame implements ActionListener {

    GeometryModel3D offFile;
    private final JFileChooser fc;
    private JFileChooser exportFC;
    private File currentFile;
    private RoffViewPanel vp;
    private boolean inApplet;
    private URL[] appletURLs;
    private RoffObserverFrame observer;
    private boolean antialiasingOn;
    private int optionsPS = RoffViewPanel.PS_NOBG;
    private int resolutionPS = 1000;

    public JRoff(boolean inApplet) {
        super("RoffView");

        this.inApplet = inApplet;
        appletURLs = null;
        observer = null;
        antialiasingOn = false;

        if (!inApplet) {
            fc = new JFileChooser();
        } else {
            fc = null;
        } 
        exportFC = null;
        currentFile = null;
        offFile = new GeometryModel3D();

        JMenuBar menubar = new JMenuBar();
        JMenu menu = new JMenu("File");
        menu.setMnemonic(KeyEvent.VK_F);
        JMenuItem menuItem;

        menuItem = new JMenuItem("Open...");
        menuItem.setActionCommand("OPEN");
        menuItem.setMnemonic(KeyEvent.VK_O);
        menuItem.addActionListener(this);
        menu.add(menuItem);

        menu.addSeparator();

        if (!inApplet) {
            menuItem = new JMenuItem("Save");
            menuItem.setActionCommand("SAVE");
            menuItem.setMnemonic(KeyEvent.VK_S);
            menuItem.addActionListener(this);
            menu.add(menuItem);

            menuItem = new JMenuItem("Save As...");
            menuItem.setActionCommand("SAVEAS");
            menuItem.setMnemonic(KeyEvent.VK_A);
            menuItem.addActionListener(this);
            menu.add(menuItem);

            menuItem = new JMenuItem("Export as PostScript...");
            menuItem.setActionCommand("EXPORT");
            menuItem.setMnemonic(KeyEvent.VK_P);
            menuItem.addActionListener(this);
            menu.add(menuItem);

            menu.addSeparator();
        }

        menuItem = new JMenuItem("Exit");
        menuItem.setActionCommand("EXIT");
        menuItem.setMnemonic(KeyEvent.VK_X);
        menuItem.addActionListener(this);
        menu.add(menuItem);

        menubar.add(menu);

        menu = new JMenu("View");
        menu.setMnemonic(KeyEvent.VK_V);

        menuItem = new JMenuItem("View Model Information...");
        menuItem.setActionCommand("V_MODELINFO");
        menuItem.setMnemonic(KeyEvent.VK_I);
        menuItem.addActionListener(this);
        menu.add(menuItem);

        menu.addSeparator();

        menuItem = new JMenuItem("Set View Parameters...");
        menuItem.setActionCommand("V_SETVIEW");
        menuItem.setMnemonic(KeyEvent.VK_V);
        menuItem.addActionListener(this);
        menu.add(menuItem);

        menuItem = new JMenuItem("Set to Default View");
        menuItem.setActionCommand("V_SETDEFAULT");
        menuItem.setMnemonic(KeyEvent.VK_D);
        menuItem.addActionListener(this);
        menu.add(menuItem);

        menu.addSeparator();

        ButtonGroup bg = new ButtonGroup();

        JRadioButtonMenuItem rbmenuItem;
        rbmenuItem = new JRadioButtonMenuItem("Wireframe", true);
        rbmenuItem.setActionCommand("V_WIRE");
        rbmenuItem.setMnemonic(KeyEvent.VK_W);
        rbmenuItem.addActionListener(this);
        bg.add(rbmenuItem);
        menu.add(rbmenuItem);

        rbmenuItem = new JRadioButtonMenuItem("Wireframe w/o Hidden Lines", true);
        rbmenuItem.setActionCommand("V_WIRE_NOHID");
        rbmenuItem.setMnemonic(KeyEvent.VK_H);
        rbmenuItem.addActionListener(this);
        bg.add(rbmenuItem);
        menu.add(rbmenuItem);

        rbmenuItem = new JRadioButtonMenuItem("Shaded");
        rbmenuItem.setActionCommand("V_SOLID");
        rbmenuItem.setMnemonic(KeyEvent.VK_S);
        rbmenuItem.addActionListener(this);
        bg.add(rbmenuItem);
        menu.add(rbmenuItem);

        rbmenuItem = new JRadioButtonMenuItem("Points");
        rbmenuItem.setActionCommand("V_POINTS");
        rbmenuItem.setMnemonic(KeyEvent.VK_P);
        rbmenuItem.addActionListener(this);
        bg.add(rbmenuItem);
        menu.add(rbmenuItem);

        menu.addSeparator();

        bg = new ButtonGroup();

        rbmenuItem = new JRadioButtonMenuItem("Normal Lines", true);
        rbmenuItem.setActionCommand("V_NORMAL");
        rbmenuItem.setMnemonic(KeyEvent.VK_N);
        rbmenuItem.addActionListener(this);
        bg.add(rbmenuItem);
        menu.add(rbmenuItem);

        rbmenuItem = new JRadioButtonMenuItem("Anti-aliased Lines", true);
        rbmenuItem.setActionCommand("V_ANTIALIAS");
        rbmenuItem.setMnemonic(KeyEvent.VK_A);
        rbmenuItem.addActionListener(this);
        bg.add(rbmenuItem);
        menu.add(rbmenuItem);

        menubar.add(menu);

        menu = new JMenu("Control Mode");
        menu.setMnemonic(KeyEvent.VK_C);

        bg = new ButtonGroup();

        rbmenuItem = new JRadioButtonMenuItem("Viewing Mode", true);
        rbmenuItem.setActionCommand("MODE_VIEW");
        rbmenuItem.setMnemonic(KeyEvent.VK_V);
        rbmenuItem.addActionListener(this);
        bg.add(rbmenuItem);
        menu.add(rbmenuItem);

        rbmenuItem = new JRadioButtonMenuItem("Editing Mode");
        rbmenuItem.setActionCommand("MODE_EDIT");
        rbmenuItem.setMnemonic(KeyEvent.VK_E);
        rbmenuItem.addActionListener(this);
        bg.add(rbmenuItem);
        menu.add(rbmenuItem);

        menubar.add(menu);

        menu = new JMenu("Tools");
        menu.setMnemonic(KeyEvent.VK_T);

        menuItem = new JMenuItem("Set Background Color");
        menuItem.setActionCommand("T_BGCOLOR");
        menuItem.setMnemonic(KeyEvent.VK_B);
        menuItem.addActionListener(this);
        menu.add(menuItem);

        menuItem = new JMenuItem("Set Surface and Lighting Properties");
        menuItem.setActionCommand("T_LIGHTING");
        menuItem.setMnemonic(KeyEvent.VK_L);
        menuItem.addActionListener(this);
        menu.add(menuItem);

        menu.addSeparator();

        menuItem = new JMenuItem("Open Observer Window");
        menuItem.setActionCommand("OBSERVER");
        menuItem.setMnemonic(KeyEvent.VK_O);
        menuItem.addActionListener(this);
        menu.add(menuItem);

        menubar.add(menu);

        this.setJMenuBar(menubar);

        vp = new RoffViewPanel();
        vp.setOff(offFile);

        this.getContentPane().add(vp, BorderLayout.CENTER);

        this.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
        this.addWindowListener(new WindowAdapter() {
                                   public void windowClosing(WindowEvent e) {
                                       exitHandler();
                                   }
                               });
        this.pack();
    }

    public static void main(String[] args) {

        JRoff mainFrame = new JRoff(false);
        mainFrame.setVisible(true);
    }

    public void setAppletURLList(URL[] list) {
        appletURLs = list;
    }

    public void actionPerformed(ActionEvent e) {

        String action = e.getActionCommand();
        
        if (action.equals("EXIT")) {
            exitHandler();
        } else if (action.equals("OPEN")) {
            openFile();
        } else if (action.equals("SAVE")) {
            saveFile();
        } else if (action.equals("SAVEAS")) {
            saveFileAs();
        } else if (action.equals("EXPORT")) {
            export();
        } else if (action.equals("V_MODELINFO")) {
            showInfo();
        } else if (action.equals("V_SETDEFAULT")) {
            vp.setDefaultPerspective();
        } else if (action.equals("V_SETVIEW")) {
            changeView();
        } else if (action.equals("V_WIRE")) {
            if (antialiasingOn) {
                vp.setViewingStyle(RoffViewPanel.VIEW_WIREFRAME_AA);
            } else {
                vp.setViewingStyle(RoffViewPanel.VIEW_WIREFRAME);
            }
        } else if (action.equals("V_WIRE_NOHID")) {
            if (antialiasingOn) {
                vp.setViewingStyle(RoffViewPanel.VIEW_WIREFRAME_NOHIDDENLINES_AA);
            } else {
                vp.setViewingStyle(RoffViewPanel.VIEW_WIREFRAME_NOHIDDENLINES);
            }
        } else if (action.equals("V_SOLID")) {
            vp.setViewingStyle(RoffViewPanel.VIEW_SOLID);
        } else if (action.equals("V_POINTS")) {
            vp.setViewingStyle(RoffViewPanel.VIEW_POINTS);
        } else if (action.equals("MODE_EDIT")) {
            vp.setControlStyle(RoffViewPanel.CONTROL_EDITING);
        } else if (action.equals("MODE_VIEW")) {
            vp.setControlStyle(RoffViewPanel.CONTROL_VIEWING);
        } else if (action.equals("T_BGCOLOR")) {
            changeBackground();
        } else if (action.equals("T_LIGHTING")) {
            changeLighting();
        } else if (action.equals("OBSERVER")) {
            showObserver();
        } else if (action.equals("OBSERVER_CLOSED")) {
            hideObserver();
        } else if (action.equals("V_NORMAL")) {
            antialiasingOn = false;
            if (vp.getViewingStyle() == RoffViewPanel.VIEW_WIREFRAME_AA) {
                vp.setViewingStyle(RoffViewPanel.VIEW_WIREFRAME);
            } else if (vp.getViewingStyle() == RoffViewPanel.VIEW_WIREFRAME_NOHIDDENLINES_AA) {
                vp.setViewingStyle(RoffViewPanel.VIEW_WIREFRAME_NOHIDDENLINES);
            }
        } else if (action.equals("V_ANTIALIAS")) {
            antialiasingOn = true;
            if (vp.getViewingStyle() == RoffViewPanel.VIEW_WIREFRAME) {
                vp.setViewingStyle(RoffViewPanel.VIEW_WIREFRAME_AA);
            } else if (vp.getViewingStyle() == RoffViewPanel.VIEW_WIREFRAME_NOHIDDENLINES) {
                vp.setViewingStyle(RoffViewPanel.VIEW_WIREFRAME_NOHIDDENLINES_AA);
            }
        }
    }

    public void exitHandler() {
        if (inApplet) {
            hideObserver();
            this.setVisible(false);
        } else {
            System.exit(0);
        }
    }
    public static String makeInfoName(String fileName){
    	String prefix=fileName.substring(0,fileName.length()-4);
    	return prefix+"_info.txt"; 
    }
    private void openFile() {
        BufferedReader in = null;
        BufferedReader info=null; 
        if (!inApplet) {
            int fcValue = fc.showOpenDialog(this);

            if (fcValue == JFileChooser.APPROVE_OPTION) {
                currentFile = fc.getSelectedFile();
                try {
                    in = new BufferedReader(new FileReader(currentFile));
                    info=new BufferedReader(new FileReader(makeInfoName(currentFile.getAbsolutePath()))); 
                 } catch (IOException e) {
                     JOptionPane.showMessageDialog(this,
                                                   "The specified file was not able to be loaded. Please check the format.",
                                                   "Unable to Load File",
                                                   JOptionPane.ERROR_MESSAGE);
                     in = null;
                 }
            }
        } else {
            if (appletURLs != null && appletURLs.length > 0) {

                RoffAppletOpenPanel raop = new RoffAppletOpenPanel(appletURLs);

                int option = JOptionPane.showConfirmDialog(this,
                                      raop,
                                      "Choose an OFF File to Open",
                                      JOptionPane.OK_CANCEL_OPTION,
                                      JOptionPane.PLAIN_MESSAGE
                                      );
                if (option == JOptionPane.OK_OPTION) {

                    int index = raop.getSelectedIndex();

                    try {
                        in = new BufferedReader(new InputStreamReader(appletURLs[index].openStream()));
                    } catch (IOException e) {
                        JOptionPane.showMessageDialog(this,
                                                   "The specified file was not able to be loaded. Please check the format.",
                                                   "Unable to Load File",
                                                   JOptionPane.ERROR_MESSAGE);
                        in = null;
                    }
                }
            }
        }

        if (in != null) {
            try {
                if (offFile.read(in,info)) {
                    if (observer != null) {
                        observer.setOff(offFile);
                    }
                    vp.setOff(offFile);
                    if (observer != null) {
                        observer.setDefaultPerspective();
                    }
                    this.setTitle("RoffView - " + currentFile.getName());
                 } else {
                     JOptionPane.showMessageDialog(this,
                                                  "The specified file was not able to be loaded. Please check the format.",
                                                  "Unable to Load File",
                                                  JOptionPane.ERROR_MESSAGE);
                 }
                 in.close();
             } catch (IOException e) {
                 JOptionPane.showMessageDialog(this,
                                               "The specified file was not able to be loaded. Please check the format.",
                                               "Unable to Load File",
                                               JOptionPane.ERROR_MESSAGE);
             }
        }
    }

    private void saveFile() {

        if (currentFile != null) {
            try {
                PrintWriter out = new PrintWriter(new FileWriter(currentFile));
                if (offFile.write(out)) {
                    JOptionPane.showMessageDialog(this,
                                                  "The file has been saved.",
                                                  "File Saved",
                                                  JOptionPane.INFORMATION_MESSAGE);
                } else {
                    JOptionPane.showMessageDialog(this,
                                                  "An error occured while trying to save the current file.",
                                                  "Unable to Save File",
                                                  JOptionPane.ERROR_MESSAGE);
                }
                out.close();
            } catch (IOException e) {
                JOptionPane.showMessageDialog(this,
                                              "An error occured while trying to save the current file.",
                                              "Unable to Save File",
                                              JOptionPane.ERROR_MESSAGE);
            }
        } else {
            saveFileAs();
        }

    }

    private void saveFileAs() {

        if (!inApplet) {
            int fcValue = fc.showSaveDialog(this);

            if (fcValue == JFileChooser.APPROVE_OPTION) {
                 boolean draw = true;
                 File newFile = fc.getSelectedFile();
                 if (newFile.exists()) {
                     draw = JOptionPane.OK_OPTION == 
                            JOptionPane.showConfirmDialog(this,
                                                    "Overwrite existing " + newFile.getName() + "?",
                                                    "Overwrite Existing File?",
                                                    JOptionPane.WARNING_MESSAGE);
                 }
                 if (draw) {
                     currentFile = newFile;
                     this.setTitle("RoffView - " + currentFile.getName());
                     saveFile();
                 }
            }
        }
    }

    private void export() {

        if (!inApplet) {

            if (exportFC == null) {
                exportFC = new JFileChooser(fc.getCurrentDirectory());
            }

            RoffPostScriptPanel rpsp = new RoffPostScriptPanel(optionsPS, resolutionPS);

            int optionBoxValue = JOptionPane.showConfirmDialog(this,
                                             rpsp,
                                             "PostScript Export Options",
                                             JOptionPane.OK_CANCEL_OPTION,
                                             JOptionPane.PLAIN_MESSAGE);
            if (optionBoxValue == JOptionPane.OK_OPTION) {

                optionsPS = rpsp.getOptions();
                resolutionPS = rpsp.getResolution();
                int fcValue = exportFC.showSaveDialog(this);

                if (fcValue == JFileChooser.APPROVE_OPTION) {
                    boolean draw = true;
                    File exportFile = exportFC.getSelectedFile();

                    // If exportFile.exists(), prompt for overwrite.
                    if (exportFile.exists()) {
                        draw =  JOptionPane.OK_OPTION == 
                                JOptionPane.showConfirmDialog(this,
                                                        "Overwrite existing " + exportFile.getName() + "?",
                                                        "Overwrite Existing File?",
                                                        JOptionPane.WARNING_MESSAGE);
                    }
                    if (draw) {
                        try {
                            PrintWriter out = new PrintWriter(new FileWriter(exportFile));
                            doExport(out, resolutionPS, optionsPS);
                            JOptionPane.showMessageDialog(this,
                                                  "Current image has been written to file.",
                                                  "Image Exported Successfully",
                                                  JOptionPane.INFORMATION_MESSAGE);
                        out.close();
                        } catch (IOException e) {
                            JOptionPane.showMessageDialog(this,
                                                  "An error occured while trying to export image. Check file permissions.",
                                                  "Unable to Export Image to File",
                                                   JOptionPane.ERROR_MESSAGE);
                        }
                    }
                }
            }
        }
    }

    private void doExport(PrintWriter out, int scale, int options) {

        out.println("%!");
        out.println("% PostScript file generated by RoffView 3.1 (Java Version) by Ryan Holmes - Ryan.Holmes@asu.edu");
        double scaleHalf = scale / 2.0;
        double scaleFactor = 228.0 / scaleHalf;
        out.println(scaleFactor + " " + scaleFactor + " scale");
        // Center of page is 300, 420
        out.println((300 / scaleFactor - scaleHalf) + " " + (420 / scaleFactor - scaleHalf) + " translate");
        out.println(0.5 / scaleFactor + " setlinewidth");
        // Bevel the line joins
        out.println("2 setlinejoin");
        // Ask the viewing pane to draw itself.
        vp.drawPostScript(out, scale, options);
        out.println("showpage");
    }

    private void showInfo() {
        JOptionPane.showMessageDialog(this,
                                      new RoffModelInfoPanel(offFile.numVertices,
                                                             offFile.numPolygons,
                                                             offFile.numEdges,
                                                             offFile.numUniqueEdges,
                                                             offFile.validUniqueCount,
                                                             offFile.largestPolygonSize,
                                                             vp.getNumberOfTriangles()
                                                            ),
                                      "Current Model Information",
                                      JOptionPane.PLAIN_MESSAGE
                                      );
    }

    private void showObserver() {
        if (observer == null) {
            observer = new RoffObserverFrame(this);
            observer.setOff(offFile);
            observer.setObservedPerspective(vp.getPerspective());
            observer.setVisible(true);
        } else {
            observer.setVisible(true);
        }
    }

    private void hideObserver() {
        if (observer != null) {
            observer.setVisible(false);
            observer.dispose();
            observer = null;
        }
    }

    private void changeBackground() {
        Color newColor = JColorChooser.showDialog(this, "Choose Background Color", vp.getBackgroundColor());
        if (newColor != null) {
            vp.setBackgroundColor(newColor);
        }
    }

    private void changeLighting() {
        RoffLightingPanel rlp = new RoffLightingPanel(new RoffPhong(vp.getLightingModel()));

        int option = JOptionPane.showConfirmDialog(this,
                                      rlp,
                                      "Surface and Lighting Properties",
                                      JOptionPane.OK_CANCEL_OPTION,
                                      JOptionPane.PLAIN_MESSAGE
                                      );
        if (option == JOptionPane.OK_OPTION) {
            vp.setLightingModel(rlp.getLightingModel());
            vp.fullPaint();
        }
    }

    private void changeView() {
        RoffPerspective perspec = vp.getPerspective();
        RoffViewParameterPanel rvpp = new RoffViewParameterPanel(perspec.getAt(),
                                                                 perspec.getFrom(),
                                                                 perspec.getUp(),
                                                                 perspec.getAngle()
                                                                );

        int option = JOptionPane.showConfirmDialog(this,
                                      rvpp,
                                      "Viewing Space Parameters",
                                      JOptionPane.OK_CANCEL_OPTION,
                                      JOptionPane.PLAIN_MESSAGE
                                      );
        if (option == JOptionPane.OK_OPTION) {
            perspec.setAt(rvpp.getAt());
            perspec.setFrom(rvpp.getFrom());
            perspec.setUp(rvpp.getUp());
            perspec.setAngle(rvpp.getAngle());
            vp.fullPaint();
        }
    }
}